using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Reflection;

namespace Calibur {
	public enum LogLevel {
		TRACE	= 1,
		DEBUG	= 2,
		INFO	= 3,
		WARN	= 4,
		ERROR	= 5,
		FATAL	= 6
	}

	public class Logger {
		public static LogLevel level = LogLevel.TRACE;
		private static Type unityDebug;
		
		static Logger() {
			unityDebug = null;
			foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) {
				if (ass.GetName().Name.Equals("UnityEngine")) {
					foreach (var t in ass.GetTypes()) {
						if (t.Name.Equals("Debug")) {
							unityDebug = t;
							return;
						}
					}
				}
			}
		}
		
		private static void UnityLog(LogLevel l, Object obj) {
			if ((int)l < (int)LogLevel.WARN) {
				unityDebug.InvokeMember("Log", 
					BindingFlags.Default | BindingFlags.InvokeMethod, 
					null, null, new object[] {obj});
			} else if ((int)l == (int)LogLevel.WARN) {
				unityDebug.InvokeMember("LogWarning", 
					BindingFlags.Default | BindingFlags.InvokeMethod, 
					null, null, new object[] {obj});
			} else {
				unityDebug.InvokeMember("LogError", 
					BindingFlags.Default | BindingFlags.InvokeMethod, 
					null, null, new object[] {obj});
			}
			
		}

		private static void Log(LogLevel l, string format, params Object[] args) {
			if ((int)l >= (int)level) {
				StackTrace stack = new StackTrace(true);
				StackFrame frame = stack.GetFrame(2);

				StringWriter sw = new StringWriter();
				sw.Write("[{0}] {1}:{2} ", l.ToString(),
						frame.GetFileName(), frame.GetFileLineNumber());
				if (args != null) {
					sw.WriteLine(format, args);
				} else {
					sw.WriteLine(format);
				}
				if (unityDebug != null) {
					UnityLog(l, sw.ToString());
				} else {
					Console.WriteLine(sw.ToString());
				}
			}
		}

		public static void Trace(string format, params Object[] args) {
			Log(LogLevel.TRACE, format, args);
		}

		public static void Trace(Object obj) {
			Log(LogLevel.TRACE, obj.ToString(), null);
		}

		public static void Debug(string format, Object[] args) {
			Log(LogLevel.DEBUG, format, args);
		}

		public static void Debug(Object obj) {
			Log(LogLevel.DEBUG, obj.ToString(), null);
		}

		public static void Info(string format, Object[] args) {
			Log(LogLevel.INFO, format, args);
		}

		public static void Info(Object obj) {
			Log(LogLevel.INFO, obj.ToString(), null);
		}

		public static void Warn(string format, Object[] args) {
			Log(LogLevel.WARN, format, args);
		}

		public static void Warn(Object obj) {
			Log(LogLevel.WARN, obj.ToString(), null);
		}

		public static void Error(string format, Object[] args) {
			Log(LogLevel.ERROR, format, args);
		}

		public static void Error(Object obj) {
			Log(LogLevel.ERROR, obj.ToString(), null);
		}

		public static void Fatal(string format, Object[] args) {
			Log(LogLevel.FATAL, format, args);
		}

		public static void Fatal(Object obj) {
			Log(LogLevel.FATAL, obj.ToString(), null);
		}
		
		public static String ToHexString(byte[] bytes) {
			StringWriter sw = new StringWriter();

			int count = bytes.Length / 16;
			int reset = bytes.Length % 16;
					
			int i = 0;
			for (i = 0; i < count; ++i) {
				PrintHexLine(sw, bytes, 16 * i, 16);
			}

			if (reset > 0) {
				PrintHexLine(sw, bytes, 16 * i, reset);
			}

			return sw.ToString();
		}
		
		private static void PrintHexLine(StringWriter writer, byte[] bytes, int start, int cnt) {
			writer.Write("[{0:D3}:{1:D3}] ", start, start + cnt - 1);
			for (int i = 0; i < cnt; ++i) {
				writer.Write("{0:X2} ", bytes[start + i]);
				if (i == 7) {
					writer.Write(" ");
				}
			}
			writer.WriteLine();
		}
	}
}